package com.xrd.uart.interaction;

import android.annotation.SuppressLint;
import android.icu.util.LocaleData;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;


import com.xrd.uart.util.DataUtils;
import com.xrd.uart.util.StringTool;
import com.xrd.uart.util.ThreadPools;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Observer;
import java.util.Vector;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;

/**
 * Creatd by Yumiko
 * All is boring expect MeiZi.
 * on 2019/11/28
 */
public abstract class ReaderHelper {


    private int MESSAGE_LOST_CONNECT = 1;


    private int MESSAGE_RESULT_SEND = 2;
    private int MESSAGE_RESULT_RECEIVE = 3;
    private int MESSAGE_RESULT_LOST_CONNECT = 4;
    private int MESSAGE_RESULT_TIMEOUT = 5;

    protected ReaderBase mReader;
    protected RXTXListener mListener;

    protected RXForResultListener mResultListener;

    private static final String TAG = "ReaderHelper";

    /*
    private final Handler mReaderHandler = new Handler(Looper.getMainLooper()) {
        @SuppressLint("HandlerLeak")
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            int what = msg.what;
            if (what == MESSAGE_RESULT_SEND) {
                mResultListener.sendData((byte[]) msg.obj);

            }
            if (what == MESSAGE_RESULT_RECEIVE) {
                mResultListener.receiveData((byte[]) msg.obj);
            }

            if (what == MESSAGE_RESULT_LOST_CONNECT) {
                mResultListener.onLostConnect();
            }

            if (what == MESSAGE_RESULT_TIMEOUT) {
                mResultListener.onTimeOut();
            }

        }
    };
     */


    public ReaderHelper() {
    }

    public void setReader(InputStream in, OutputStream out, DataPackageParser parser, DataPackageProcess process) throws Exception {
        if (in != null && out != null) {
            if (this.mReader == null) {
                this.mReader = new ReaderBase(this, in, out, parser, process) {
                    protected void onLostConnect() {
                        ReaderHelper.this.onLostConnect();
                    }

                    protected void reciveData(byte[] btAryReceiveData) {
                        ReaderHelper.this.reciveData(btAryReceiveData);
                    }

                    protected void sendData(byte[] btArySendData) {
                        ReaderHelper.this.sendData(btArySendData);
                    }
                };
            }

        } else {
            throw new NullPointerException("in Or out is NULL!");
        }

    }

    public void setRXTXListener(RXTXListener listener) {
        this.mListener = listener;

    }

    private void onLostConnect() {
        if (this.mListener != null) {
            mListener.onLostConnect();
        }

    }

    private void reciveData(byte[] btAryReceiveData) {
        if (this.mListener != null) {
            this.mListener.receiveData(btAryReceiveData);
        }

    }

    private void sendData(byte[] btArySendData) {
        if (this.mListener != null) {
            this.mListener.sendData(btArySendData);
        }

    }


    public int sendCommand(byte[] btCMDPackage) {
        if (this.mReader == null) {
            throw new NullPointerException("Please build the connection first!");
        } else {
            return this.mReader.sendBuffer(btCMDPackage);
        }
    }


    public void sendCommandForReSultListener(byte[] btCMDPackage, RXForResultListener rxForResultListener) {
        mResultListener = rxForResultListener;
        if (mReader == null) {
            throw new NullPointerException("Please build the connection first!");
        }
        mReader.sendBufferForResultListener(btCMDPackage);

    }


    public byte[] sendCommandHasReSult(byte[] btCMDPackage, long mills) {

        if (this.mReader == null) {
            throw new NullPointerException("Please build the connection first!");
        } else {
            return this.mReader.sendBufferHasResult(btCMDPackage, mills);
        }
    }


    public byte[] sendCommandHasReSult(byte[] btCMDPackage) {

        if (this.mReader == null) {
            throw new NullPointerException("Please build the connection first!");
        } else {
            return this.mReader.sendBufferHasResult(btCMDPackage, mReader.millis);
        }
    }


    public byte[] sendCommandHasMultiReSult(final byte[] bytes) {
        if (this.mReader == null) {
            throw new NullPointerException("Please build the connection first!");
        }
        return mReader.sendBufferHasMultiResult(bytes, mReader.millis);
    }

    public byte[] sendCommandTotalReSult(final byte[] bytes) {
        if (this.mReader == null) {
            throw new NullPointerException("Please build the connection first!");
        }
        return mReader.sendBufferTotalReSult(bytes, mReader.millis);
    }


    public byte[] sendCommandHasMultiReSult(byte[] bytes, long mills) {
        if (this.mReader == null) {
            throw new NullPointerException("Please build the connection first!");
        } else {
            return this.mReader.sendBufferHasMultiResult(bytes, mills);
        }
    }


    public void signOut() {
        if (this.mReader != null) {
            this.mReader.signOut();
            this.mReader = null;
        }

        this.mListener = null;
    }


    public boolean isAlive() {
        return this.mReader != null && this.mReader.IsAlive();
    }

    public void setTimeOut(long milliseconds) {
        if (mReader != null)
            mReader.setTimeOut(milliseconds);
    }

    public void setEndFlag(byte endFlag) {
        if (mReader != null)
            mReader.setEndFlag(endFlag);
    }

    public void setLogIsShow(boolean isShow) {
        if (mReader != null)
            mReader.setLogIsShow(isShow);
    }


    public abstract class ReaderBase {

        private WaitThread mWaitThread = null;
        private InputStream mInStream = null;
        private OutputStream mOutStream = null;
        private DataPackageParser mPackageParser;
        private DataPackageProcess mPackageProcess;
        private ThreadPools.ThreadPool mExecutorService;
        private FutureTask<byte[]> mFutureTask;
        private Callable<byte[]> mWaitCallable;
        private volatile boolean isRequest = false;
        private volatile boolean isMultiRequest = false;
        private volatile boolean isTotalRequest = false;
        private volatile int mTotal = -1;
        protected long millis;
        protected byte endFlag = (byte) 0x0D;
        protected boolean logIsShow = false;


        private LinkedBlockingDeque<FutureTask> mQueue = new LinkedBlockingDeque<>();

        private volatile LinkedBlockingDeque<byte[]> mInfo = new LinkedBlockingDeque<>(1);
        private volatile LinkedBlockingDeque<byte[]> mMultiInfo = new LinkedBlockingDeque<>();
        private volatile Vector<byte[]> mTotalInfo = new Vector<>();


        public ReaderBase(ReaderHelper readerHelper, InputStream in, OutputStream out, DataPackageParser parser, DataPackageProcess process) throws Exception {

            this.mInStream = in;
            this.mOutStream = out;
            millis = 600;
            if (mExecutorService == null) {
                mExecutorService = ThreadPools.getThreadPool();
                mWaitCallable = new Callable<byte[]>() {
                    @Override
                    public byte[] call() throws Exception {
                        return mInfo.take();
                    }
                };

            }


            if (parser != null && process != null) {
                this.mPackageParser = parser;
                this.mPackageProcess = process;
                this.StartWait();
            } else {
                throw new Exception("DataPackageParser && DataPackageProcess are null exception!");
            }

        }


        public boolean IsAlive() {
            return this.mWaitThread != null && this.mWaitThread.isRunning();
        }

        private void StartWait() {
            this.mWaitThread = new WaitThread();
            mExecutorService.execute(mWaitThread);

        }


        private void StartForResultWaitListener() {
            mFutureTask = new FutureTask<>(mWaitCallable);
            mExecutorService.submit(mFutureTask);
            try {
                byte[] bytes = mFutureTask.get(millis, TimeUnit.MILLISECONDS);
                mResultListener.receiveData(bytes);
//                mReaderHandler.obtainMessage(MESSAGE_RESULT_RECEIVE, bytes).sendToTarget();
            } catch (ExecutionException | InterruptedException e) {
                if (logIsShow) e.printStackTrace();
                mResultListener.onLostConnect();
//                mReaderHandler.obtainMessage(MESSAGE_RESULT_LOST_CONNECT).sendToTarget();
            } catch (TimeoutException e) {
                if (logIsShow) e.printStackTrace();
                mResultListener.onTimeOut();
//                mReaderHandler.obtainMessage(MESSAGE_RESULT_TIMEOUT).sendToTarget();
            } finally {
                isRequest = false;
                mInfo.clear();
                mFutureTask.cancel(true);
//                SystemClock.sleep(cmdEndTime);
            }

        }

        private byte[] StartHasResultWaitMills(long m) {
            mFutureTask = new FutureTask<>(mWaitCallable);
            mExecutorService.submit(mFutureTask);
            long cmdEndTime = 0;
            try {
                byte[] bytes = mFutureTask.get(m, TimeUnit.MILLISECONDS);
                SystemClock.sleep(cmdEndTime);
                return bytes;
            } catch (ExecutionException | InterruptedException | TimeoutException e) {
                if (logIsShow) e.printStackTrace();
                ;
                return null;
            } finally {
                isRequest = false;
                mInfo.clear();
                mFutureTask.cancel(true);
                SystemClock.sleep(cmdEndTime);
            }
        }

        private byte[] StartHasMultiResultWaitMills(long m) {
            mFutureTask = new FutureTask<>(mWaitCallable);
            mExecutorService.submit(mFutureTask);
            long cmdEndTime = 0;
            try {
                byte[] bytes = mFutureTask.get(m, TimeUnit.MILLISECONDS);
                SystemClock.sleep(cmdEndTime);
                return bytes;
            } catch (ExecutionException | InterruptedException | TimeoutException e) {
                if (logIsShow) e.printStackTrace();
                return null;
            } finally {
                isMultiRequest = false;
                mInfo.clear();
                mMultiInfo.clear();
                mFutureTask.cancel(true);
                SystemClock.sleep(cmdEndTime);
            }
        }

        private byte[] StartTotalResultWaitMills(long m) {
            mFutureTask = new FutureTask<>(mWaitCallable);
            mExecutorService.submit(mFutureTask);
            long cmdEndTime = 0;
            try {
                byte[] bytes = mFutureTask.get(m, TimeUnit.MILLISECONDS);
                SystemClock.sleep(cmdEndTime);
                return bytes;
            } catch (ExecutionException | InterruptedException | TimeoutException e) {
                if (logIsShow) e.printStackTrace();
                return null;
            } finally {
                isTotalRequest = false;
                mTotal = -1;
                mInfo.clear();
                mTotalInfo.clear();
                mFutureTask.cancel(true);
                SystemClock.sleep(cmdEndTime);
            }
        }


        public void registerObserver(Observer observer) {
            this.mPackageProcess.addObserver(observer);
        }

        public void setTimeOut(long milliseconds) {
            mReader.millis = milliseconds;
        }

        public void setEndFlag(byte endFlag) {
            this.endFlag = endFlag;
        }

        public void setLogIsShow(boolean logIsShow) {
            this.logIsShow = logIsShow;
        }

        public int sendBuffer(byte[] completeCMD) {
            return this.sendMessage(completeCMD);
        }

        private void sendBufferForResultListener(final byte[] btCMDPackage) {
            if (!mInfo.isEmpty()) mInfo.clear();
            ThreadPools.getThreadPool().execute(new Runnable() {
                @Override
                public void run() {
                    sendMessageForResultListener(btCMDPackage);
                }
            });
        }

        private byte[] sendBufferHasResult(byte[] btCMDPackage, long mills) {
            if (!mInfo.isEmpty()) mInfo.clear();
            return sendMessageHasResult(btCMDPackage, mills);
        }


        private byte[] sendBufferHasMultiResult(byte[] btCMDPackage, long mills) {
            if (!mInfo.isEmpty()) mInfo.clear();
            return sendMessageHasMultiResult(btCMDPackage, mills);
        }

        private byte[] sendBufferTotalReSult(byte[] btCMDPackage, long mills) {
            if (!mInfo.isEmpty()) mInfo.clear();
            return sendMessageBufferResult(btCMDPackage, mills);
        }


        private synchronized int sendMessage(byte[] btArySenderData) {
            isRequest = false;
            try {
                this.mOutStream.write(btArySenderData);
            } catch (IOException var4) {
                this.onLostConnect();
                return -1;
            } catch (Exception var5) {
                return -1;
            }
            this.sendData(btArySenderData);
            return 0;
        }

        private synchronized int sendMessageForResultListener(byte[] btArySenderData) {
            isRequest = true;
            try {
                this.mOutStream.write(btArySenderData);
                sendDataForReSult(btArySenderData);
                StartForResultWaitListener();
                return 0;
            } catch (IOException var4) {
                onLostConnect();
                return -1;
            }
        }


        private synchronized byte[] sendMessageHasResult(byte[] btArySenderData, long mills) {
            isRequest = true;
            try {
                this.mOutStream.write(btArySenderData);
            } catch (IOException var4) {
                this.onLostConnect();
                return null;
            } catch (Exception var5) {
                return null;
            }
            return StartHasResultWaitMills(mills);
        }

        private synchronized byte[] sendMessageHasMultiResult(byte[] btArySenderData, long mills) {
            isMultiRequest = true;
            try {
                this.mOutStream.write(btArySenderData);
            } catch (IOException var4) {
                this.onLostConnect();
                return null;
            } catch (Exception var5) {
                return null;
            }
            return StartHasMultiResultWaitMills(mills);
        }

        private synchronized byte[] sendMessageBufferResult(byte[] btArySenderData, long mills) {
            isTotalRequest = true;
            mTotal = DataUtils.HexToInt(DataUtils.ByteArrToHex(btArySenderData, 4, 6));
            try {
                this.mOutStream.write(btArySenderData);
            } catch (IOException var4) {
                this.onLostConnect();
                return null;
            } catch (Exception var5) {
                return null;
            }
            return StartTotalResultWaitMills(mills);
        }


        public void unRegisterObserver(Observer observer) {
            this.mPackageProcess.deleteObserver(observer);
        }

        public void signOut() {

            if (this.mWaitThread != null) {
                this.mWaitThread.signOut();
                this.mWaitThread = null;
            }

            try {
                if (this.mInStream != null) {
                    this.mInStream.close();
                }

                if (this.mOutStream != null) {
                    this.mOutStream.close();
                }
            } catch (IOException var2) {
                var2.printStackTrace();
            }

            this.unRegisterObservers();
            this.mPackageParser = null;
            this.mPackageProcess = null;
        }

        private void unRegisterObservers() {
            this.mPackageProcess.deleteObservers();
        }

        /**
         * mMultiInfo：放入多次返回数据，有时候连续返回2次数据，结束符有可能不是最后一个
         */

        private class WaitThread implements Runnable {
            private boolean mShouldRunning = true;

            private WaitThread() {
                this.mShouldRunning = true;
            }

            public void run() {
                byte[] btAryBuffer = new byte[4096];

                while (this.mShouldRunning) {
                    try {
                        int nLenRead = ReaderBase.this.mInStream.read(btAryBuffer);
                        if (nLenRead > 0) {
                            byte[] btAryReceiveData = new byte[nLenRead];
                            System.arraycopy(btAryBuffer, 0, btAryReceiveData, 0, nLenRead);
                            if (isRequest) {//只有一次返回
                                if (!mInfo.isEmpty()) mInfo.clear();
                                mInfo.add(btAryReceiveData);
                            } else if (isMultiRequest) {//多次数据返回的组合
                                mMultiInfo.add(btAryReceiveData);
                                if (btAryReceiveData[nLenRead - 1] == endFlag) {//最后一个字节换行符
                                    byte[] merger = mMultiInfo.poll();//取出一个元素
                                    while (!mMultiInfo.isEmpty()) {//取完不为空
                                        merger = StringTool.byteMerger(merger, mMultiInfo.poll());
                                    }
                                    mInfo.add(merger);
                                    mMultiInfo.clear();
                                    if (isMultiRequest) isMultiRequest = false;
                                }
                            } else if (isTotalRequest && mTotal > 0) {
                                mTotalInfo.add(btAryReceiveData);
                                int m = 0;
                                for (int i = 0; i < mTotalInfo.size(); i++) {
                                    m += mTotalInfo.get(i).length;
                                }
                                if (logIsShow) {
                                    Log.d(TAG, "run: m 数量 = " + m + "     mTotal = " + mTotal);
                                }
                                if (m == mTotal + 5) {
                                    byte[] merger = mTotalInfo.get(0);//取出一个元素
                                    for (int i = 1; i < mTotalInfo.size(); i++) {
                                        merger = StringTool.byteMerger(merger, mTotalInfo.get(i));
                                    }
                                    mInfo.add(merger);
                                    mTotalInfo.clear();
                                    if (isTotalRequest) isTotalRequest = false;
                                    mTotal = -1;
                                }

                            } else {
                                ReaderBase.this.reciveData(btAryReceiveData);
                                ReaderBase.this.mPackageParser.runReceiveDataCallback(btAryReceiveData, ReaderBase.this.mPackageProcess);
                            }
                            if (isRequest) isRequest = false;
                        }
                    } catch (IOException var4) {
                        var4.printStackTrace();
                        ReaderBase.this.onLostConnect();
                        return;
                    }
                }
                ReaderBase.this.onLostConnect();
            }

            public void signOut() {
                this.mShouldRunning = false;
            }

            public boolean isRunning() {
                return mShouldRunning;
            }
        }


        protected void sendDataForReSult(byte[] btArySenderData) {
            if (mResultListener != null) {
//                mReaderHandler.obtainMessage(MESSAGE_RESULT_SEND, btArySenderData).sendToTarget();
                mResultListener.sendData(btArySenderData);
            }
        }

        protected abstract void onLostConnect();

        protected abstract void reciveData(byte[] btAryReceiveData);

        protected abstract void sendData(byte[] var1);


    }


}
